﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

using RSEngine.Math;
using RSEngine.Core;
using RSEngine.Helper.OGL;

using Tao.OpenGl;

#region Copyright (c) 2006, 2007, 2008, 2009 Render Studio Foundation. All rights reserved.

/*
 Copyright (c) 2006, 2007, 2008, 2009 Render Studio Foundation. All rights reserved.
 This work is copyrighted in its entirety. Permission to use "Render Studio"
 material may be granted only after we receive a written request from you,
 and you receive written permission from us.

 While Render Studio Foundation provides the information herein to anyone,
 we retain copyright on all code, text, graphic images, and other content.
 This means that you may not distribute the code, the text or graphics to others without the
 express written permission of Render Studio Foundation; "mirror" this information on your server
 without our permission; or modify or reuse the code, text or graphics on our servers.
 You may print copies of the information for your own personal use; and, you may reference
 the Service from your own documents. Any commercial use of this material is prohibited without
 the written permission of Render Studio Foundation. In all copies of this information, you must
 retain this notice and any other copyright notices originally included with such information.

 This material may be protected by U.S. and International Copyright laws, and you are legally
 bound by the copyright notices on those systems.

 "Render Studio" logos and other official symbols or graphics may not be used in connection with any
 product or service that has not been expressly authorized in advance in writing or in any manner
 that is likely to cause confusion among the users of this or other Web sites or among the public.
 All other seals, graphics or trademarks of organizations not affiliated with Render Studio Foundation
 that appear on this site are the property of their respective owners.

 Render Studio Foundation reserve all other rights. 
*/

#endregion

#region Render Studio Source Code Licence

/*
This license governs use of the Render Studio software. If you use the software, you accept this license. If you do not accept the license, do not use the software.

1. Summary

The source code is made available to view for reference purposes or to contribute to the project only. Developers may not distribute or modify the code for commercial or non-commercial purposes without express Agreement of Render Studio Foundation.

2. Definitions

The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law.
"Licensor" means the foundation "Render Studio Foundation".
"You" means the licensee of the software, who is not engaged in designing, developing, or testing other software, that has the same or substantially the same features or functionality as the software.
"Your company" means the company you worked for when you downloaded the software.
"Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes of debugging and maintaining your products. For clarity, "reference use" does NOT include (a) the right to use the software for purposes of designing, developing, or testing other software, that has the same or substantially the same features or functionality as the software, and (b) the right to distribute the software outside of your company.
"Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor under this license.

3. Grant of Rights

(A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free copyright license to reproduce the software for reference use.
(B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free patent license under licensed patents for reference use.
4. Limitations
(A) No Trademark License- This license does not grant you any rights to use the Licensor's name, logo, or trademarks.
(B) If you begin patent litigation against the Licensor over patents that you think may apply to the software (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically.
(C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
 */

#endregion

namespace RSEngine.Tools.Selection
{

    /// <summary>
    /// Define the mode used for the selection tool.
    /// </summary>
    public enum SelectionMode
    {
        //None,
        Geometry,
        Face,
        //Edge,
        //Vertex,
        //Normal
    }

    sealed public class GLSelection
    {

        #region Variables

        public SelectionMode Mode = SelectionMode.Geometry;

        //---- Colors
        private float Red;
        private float Green;
        private float Blue;

        //---- Colors table
        //private Dictionary<RGBIndex, object> _colorMaps = new Dictionary<RGBIndex, object>();
        private List<object> _colorMaps = new List<object>(0);

        private System.Drawing.Bitmap _selectionBitmap = new System.Drawing.Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        //---- Open GL display Lists
        private DisplayListManager _meshDLM = new DisplayListManager();
        private DisplayListManager _normalDLM = new DisplayListManager();

        //---- Selection
        public List<object> CurrentSelection = new List<object>();

        //---- Matrices stack
        private Stack<float[]> _matrices = new Stack<float[]>();

        #endregion

        #region Select

        /// <summary>
        /// Start object picking.
        /// </summary>
        public object Select(Scene scene, float x, float y, float imageWidth, float imageHeight, Camera camera)
        {
            Red = Green = Blue = 0;
            _colorMaps = new List<object>(_colorMaps.Count);

            Gl.glDrawBuffer(Gl.GL_BACK);
            Gl.glReadBuffer(Gl.GL_BACK);

            Gl.glViewport(0, 0, (int)imageWidth, (int)imageHeight);

            #region Look at

            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();

            if (camera.Projection == CameraProjection.Perspective)
                Glu.gluPerspective(camera.FieldOfView, camera.AspectRatio, 1, 10000);
            else
            {
                //// Set the orthogonal view
                //Gl.glOrtho(-_aspectRatio * VIEWPORT_SPACE_SIZE / 2, _aspectRatio * VIEWPORT_SPACE_SIZE / 2, VIEWPORT_SPACE_SIZE / 2, -VIEWPORT_SPACE_SIZE / 2, 0, 10000);

                //// Set the zoom factor
                //Gl.glScaled(_orthoScale, _orthoScale, _orthoScale);
            }

            // Look at : normalize the distance
            Vector3 eye = camera.Eye.Clone();
            Vector3 eyeAt = camera.EyeAt.Clone();
            Vector3 eyeNormal = camera.EyeUp.Clone();

            Glu.gluLookAt(
                eye.x, eye.y, eye.z,
                eyeAt.x, eyeAt.y, eyeAt.z,
                eyeNormal.x, eyeNormal.y, eyeNormal.z);

            #endregion

            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();

            #region Clear the buffer

            Gl.glClearColor(0f, 0f, 0f, 0f);
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

            #endregion

            //Gl.glShadeModel(Gl.GL_FLAT);  // Set the shading model to smooth 

            //---- Initialize
            Gl.glPolygonMode(Gl.GL_FRONT_AND_BACK, Gl.GL_FILL);
            Gl.glEnable(Gl.GL_COLOR_MATERIAL);
            Gl.glDisable(Gl.GL_LIGHTING);

            // Necessary ?
            //float[] al = new float[] { 0f, 0f, 0f, 1.0f };
            //Gl.glLightModelfv(Gl.GL_LIGHT_MODEL_AMBIENT, al);
            //Gl.glLightModeli(Gl.GL_LIGHT_MODEL_LOCAL_VIEWER, Gl.GL_TRUE);

            //---- Pass 1
            for (int index = 0; index < scene.Layers.Count; index++)
            {
                Layer layer = scene.Layers[index];
                if (layer.IsVisible || !layer.IsLocked)
                    Render_Layer(layer);
            }

            //---- Pass 2
            y = (int)(imageHeight - 1 - y);

            System.Drawing.Imaging.BitmapData bd = _selectionBitmap.LockBits(new System.Drawing.Rectangle(0, 0, 1, 1), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            Gl.glReadPixels((int)x, (int)y, 1, 1, Gl.GL_BGRA, Gl.GL_UNSIGNED_BYTE, bd.Scan0);
            _selectionBitmap.UnlockBits(bd);
            System.Drawing.Color c = _selectionBitmap.GetPixel(0, 0);

            //---- Search for the object
            CurrentSelection = new List<object>();
            try
            {
                // Since the color is the list index, we can use it directly
                // and don't need to search the list for it
                int index = (int)c.R + (((int)c.G) << 8) + ((((int)c.B) << 16));
                index--;
                if (index < 0 || index > _colorMaps.Count - 1)
                    return null;

                object selected = _colorMaps[index];

                if (selected is Transformable)
                    ((Transformable)selected).IsSelected = true;

                CurrentSelection.Add(selected);

                return selected;
            }
            catch { }

            return null;
        }

        #endregion

        #region Reset

        public void Reset()
        {
            _meshDLM.Reset();
            _normalDLM.Reset();
        }

        #endregion

        #region Render_Layer

        private void Render_Layer(Layer layer)
        {
            for (int index = 0; index < layer.Nodes.Count; index++)
            {
                Node node = layer.Nodes[index];
                if (!node.IsVisible || node.IsLocked)
                    continue;

                Render_Node(node);
            }

            for (int index = 0; index < layer.Geometries.Count; index++)
            {
                Geometry geometry = layer.Geometries[index];
                if (!geometry.IsVisible || geometry.IsLocked)
                    continue;

                Render_Geometry(geometry);
            }
        }

        #endregion

        #region Render_Node

        private void Render_Node(Node node)
        {
            for (int index = 0; index < node.Children.Count; index++)
            {
                Node child = node.Children[index];
                if (!child.IsVisible || child.IsLocked)
                    continue;

                Render_Node(child);
            }

            for (int index = 0; index < node.Geometries.Count; index++)
            {
                Geometry geometry = node.Geometries[index];
                if (!geometry.IsVisible || geometry.IsLocked)
                    continue;

                Render_Geometry(geometry);
            }
        }

        #endregion

        #region Render_Geometry

        private void Render_Geometry(Geometry geometry)
        {
            //---- Sub geometries
            for (int index = 0; index < geometry.Children.Count; index++)
            {
                Geometry childGeometry = geometry.Children[index];
                if (childGeometry.IsVisible && !childGeometry.IsLocked)
                    Render_Geometry(childGeometry);
            }

            //---- Apply the transformations
            LoadMatrix(geometry.WorldTransform.Values);

            //---- Set up the selection mode
            if (Mode == SelectionMode.Geometry)
            {
                float[] color = NewColor(geometry);
                Gl.glColor3fv(color);
            }

            //---- Render the geometry
            if (Mode == SelectionMode.Geometry)
            {
                _meshDLM.RenderDisplayList(
                    geometry.Id,
                    delegate { RenderSpecificGeometry(geometry); });
            }
            else
                RenderSpecificGeometry(geometry);

            //---- Draw Normals
            //if (SelectNormals)
            //    Render_Normals(geometry);
        }

        #endregion

        #region RenderSpecificGeometry

        private void RenderSpecificGeometry(Geometry geometry)
        {
            if (geometry.Type == RSType.MeshGeometry && ((MeshGeometry)geometry).Indices.Count > 0)
                RenderMeshGeometry((MeshGeometry)geometry);
        }

        #endregion

        #region RenderMeshGeometry

        private void RenderMeshGeometry(MeshGeometry polygon)
        {
            int indiceIndex = 0;
            for (int index0 = 0; index0 < polygon.FacesCount; index0++)
            {
                int count = polygon.VerticesPerFace(index0);

                // To avoid "degenerate polygon"
                //bool isDegenerated = false;
                //for (int index = 0; index < count; index++)
                //{
                //    int indice = polygon.Indices[indiceIndex];
                //    if (polygon.Vertices[indice].Normal.LengthSquared == 0)
                //    {
                //        isDegenerated = true;
                //        break;
                //    }
                //}
                //if (isDegenerated)
                //    continue;

                //---- Set up the selection mode
                if (Mode == SelectionMode.Face)
                {
                    FaceProxy face = new FaceProxy(polygon, index0);
                    float[] color = NewColor(face);
                    Gl.glColor3fv(color);
                }

                Gl.glBegin(Gl.GL_POLYGON);
                for (int index = 0; index < count; index++)
                {
                    int indice = polygon.Indices[indiceIndex];
                    Vector3 vertex = polygon.Vertices[indice].Coordinate;
                    Vector3 normal = polygon.Vertices[indice].Normal;

                    Gl.glVertex3d(vertex.x, vertex.y, vertex.z);
                    Gl.glNormal3d(normal.x, normal.y, normal.z);

                    indiceIndex++;
                }
                Gl.glEnd();
            }
        }

        #endregion

        #region Render_Light

        private void Render_Light(LightSource light)
        {
            if (light is AmbientLight)
                Render_AmbientLight((AmbientLight)light);
            else if (light is DirectionalLight)
                Render_DirectionalLight((DirectionalLight)light);
        }

        private void Render_AmbientLight(AmbientLight light)
        {
            //--- Axis
            DrawCircle(light.Position, 0.2f);
            //Gl.glVertex3dv(light.Position);
            //Gl.glVertex3dv(light.ApplyTransformation(Vector3.UnitX));
        }

        private void Render_DirectionalLight(DirectionalLight light)
        {
            //--- Axis
            LoadMatrix(light.WorldTransform.Values);

            DrawCircle(light.Position, 0.01f);
            /*
                        // X
                        Gl.glBegin(Gl.GL_LINE);
                        Gl.glColor3fv(COLOR_AXIS_X);
                        Gl.glVertex3dv(light.Position);
                        Gl.glVertex3dv(Vector3.UnitX);
                        Gl.glEnd();

                        // Y
                        Gl.glBegin(Gl.GL_LINE);
                        Gl.glColor3fv(COLOR_AXIS_Y);
                        Gl.glVertex3dv(light.Position);
                        Gl.glVertex3dv(Vector3.UnitY);
                        Gl.glEnd();

                        // Z
                        Gl.glBegin(Gl.GL_LINE);
                        Gl.glColor3fv(COLOR_AXIS_Z);
                        Gl.glVertex3dv(light.Position);
                        Gl.glVertex3dv(Vector3.UnitZ);
                        Gl.glEnd();*/
        }

        #endregion

        #region Render_Normals

        private void Render_Normals(Geometry geometry)
        {
            //try
            //{
            //    Gl.glColor3f(COLOR_NORMAL[0], COLOR_NORMAL[1], COLOR_NORMAL[2]);

            //    ---- Render the triangle geometry
            //    _normalDLM.RenderDisplayList(
            //        geometry.Id,
            //        delegate { Render_GeometryNormals(geometry); });
            //}
            //finally
            //{
            //    if (_GLModeSettings.GL_LIGHTING)
            //        Gl.glEnable(Gl.GL_LIGHTING);

            //    if (!_GLModeSettings.GL_COLOR_MATERIAL)
            //        Gl.glDisable(Gl.GL_COLOR_MATERIAL);
            //}
        }

        #endregion

        #region Render_GeometryNormals

        private void Render_GeometryNormals(Geometry geometry)
        {
            MeshGeometry polygon = (MeshGeometry)geometry;

            Gl.glBegin(Gl.GL_LINES);
            for (int index = 0; index < polygon.Vertices.Count; index++)
            {
                Vector3 vertex = polygon.Vertices[index].Coordinate;
                Vector3 normal = polygon.Vertices[index].Normal;

                Gl.glVertex3d(vertex.x, vertex.y, vertex.z);
                Gl.glVertex3d(vertex.x + normal.x, vertex.y + normal.y, vertex.z + normal.z);
            }
            Gl.glEnd();

        }

        #endregion

        #region Helpers : DrawCircle

        private void DrawCircle(Vector3 position, float radius)
        {
            Gl.glBegin(Gl.GL_LINE_LOOP);

            for (float angle = 0.0f; angle <= (2.0f * 3.14159); angle += 0.01f)
            {
                float x = position.x + (radius * (float)System.Math.Sin(angle));
                float y = position.y + (radius * (float)System.Math.Cos(angle));
                Gl.glVertex3d(x, y, position.z);
            }

            Gl.glEnd();
        }



        #endregion

        #region Helpers : PushMatrix / PopMatrix / LoadMatrix

        private void PushMatrix(float[] matrix)
        {
            _matrices.Push(matrix);
        }

        private void PopMatrix()
        {
            _matrices.Pop();
        }

        private void LoadMatrix(float[] matrix)
        {
            if (_matrices.Count == 0)
            {
                Gl.glLoadMatrixf(matrix);
                return;
            }

            Gl.glLoadIdentity();
            float[][] matrices = _matrices.ToArray();
            for (int index = 0; index < matrices.Length; index++)
                Gl.glMultMatrixf(matrices[index]);
        }

        #endregion

        #region NewColor

        private float[] NewColor(object associatedObject)
        {
            Red++;
            if (Red > 255)
            {
                Red = 0;
                Green++;
                if (Red > 255)
                {
                    Green = 0;
                    Blue++;
                }
            }

            _colorMaps.Add(associatedObject);

            return new float[] { Red / 255, Green / 255, Blue / 255 };
        }

        #endregion

    }

    #region OLD : GLSelector

    //public class GLSelector
    //{
    //    public Vector3 Origin;
    //    public Vector3 Direction;

    //    public void RayFromScreen(int sx, int sy, int sw, int sh)
    //    {
    //        float[] modelview = new float[16];
    //        float[] projection = new float[16];
    //        float x, y, z;
    //        int[] viewport = new int[4];

    //        Gl.glGetDoublev(Gl.GL_MODELVIEW_MATRIX, modelview);
    //        Gl.glGetDoublev(Gl.GL_PROJECTION_MATRIX, projection);
    //        Gl.glGetIntegerv(Gl.GL_VIEWPORT, viewport);

    //        Glu.gluUnProject(sx, sh - sy, 0.0, modelview, projection, viewport, &x, &y, &z);
    //        Origin = new Vector3(x, y, z);

    //        Glu.gluUnProject(sx, sh - sy, 1.0, modelview, projection, viewport, &x, &y, &z);
    //        Direction = new Vector3(x - Origin.x, y - Origin.y, z - Origin.z);
    //        Direction.Normalize();
    //    }

    //    public float IntersectPlane(Vector3 planPosition, Vector3 planNormal)
    //    {
    //        float d = planNormal.Dot(Direction);
    //        if (d == 0)
    //            return -1;

    //        float t = planPosition.Dot(planNormal) - Origin.Dot(planNormal) / d;
    //        if (t > 0.0001f)
    //            return t;

    //        return -1;
    //    }

    //    public void Pick(int sx, int sy, int sw, int sh)
    //    {
    //        RayFromScreen(sx, sy, sw, sh);
    //        IntersectPlane(null, null);

    //        float pickX = Origin.x + Direction.x * this;
    //        float pickY = Origin.y + Direction.y * this;
    //        float pickZ = Origin.z + Direction.z * this;
    //    }
    //}

    #endregion

}
